package com.region.http.client.request.apcache;

import com.region.common.adapter.logging.LoggerAdapter;
import com.region.common.adapter.logging.LoggerAdapterFactory;
import com.region.http.client.config.RequestAnnotationConfig;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Apache Pool Http Client
 *
 * @author liujieyu
 * @date 2023/8/12 16:05
 * @description
 */
public class ApachePoolHttpClientExecutor extends AbstractApacheHttpExecutor {

    private static final LoggerAdapter logger = LoggerAdapterFactory.getLogger(ApachePoolHttpClientExecutor.class);

    // Number of all hosts in the connection pool
    private static final int MAX_TOTAL = 200;
    // Maximum number of connections to a single host
    private static final int DEFAULT_MAX_PER_ROUTE = 20;
    // In concurrent cases, objects are prevented from being created repeatedly
    private final Lock lock = new ReentrantLock();
    // route pool
    private final Map<String, CloseableHttpClient> clientPool = new ConcurrentHashMap<>();
    // apache client request config
    private Map<String, RequestConfig> configPool = new ConcurrentHashMap<>();

    @Override
    public CloseableHttpClient getHttpClient(String url, RequestAnnotationConfig config) throws UnsupportedOperationException {
        String httpRoute = getHttpRoute(url);
        if (clientPool.containsKey(httpRoute)) {
            return clientPool.get(httpRoute);
        } else {
            // Ease the stress of creating objects
            if (clientPool.containsKey(httpRoute)) {
                return clientPool.get(httpRoute);
            }
            lock.lock();
            try {
                // Ease the stress of creating objects
                if (clientPool.containsKey(httpRoute)) {
                    return clientPool.get(httpRoute);
                }
                CloseableHttpClient httpClient = createCloseableHttpClient(config);
                clientPool.put(httpRoute, httpClient);
                return httpClient;
            } finally {
                lock.unlock();
            }
        }
    }

    @Override
    public void releaseConnection(HttpClient httpClient) throws UnsupportedOperationException {

    }

    /**
     * get url route for pool client manager
     *
     * @param url
     * @return
     */
    private String getHttpRoute(String url) {
        StringBuilder httpRoute = new StringBuilder();
        try {
            URI uri = new URI(url);
            httpRoute.append(uri.getScheme());
            httpRoute.append("://");
            httpRoute.append(uri.getHost());
            httpRoute.append(":");
            httpRoute.append(uri.getPort());
            return httpRoute.toString();
        } catch (URISyntaxException e) {
            logger.error("The uri is not an http link", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * create default PoolingHttpClientConnectionManager
     *
     * @return
     */
    private PoolingHttpClientConnectionManager createPoolingHttpClientConnectionManager() {
        PoolingHttpClientConnectionManager clientConnectionManager = new PoolingHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", getSslSocketFactory())
                        .build());
        clientConnectionManager.setMaxTotal(MAX_TOTAL);
        clientConnectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);
        return clientConnectionManager;
    }

    /**
     * create default CloseableHttpClient
     *
     * @param config
     * @return
     */
    private CloseableHttpClient createCloseableHttpClient(RequestAnnotationConfig config) {
        String key = config.getConnectTime() + String.valueOf(config.getReadTime()) + config.getRequestTime();
        RequestConfig requestConfig = null;
        if (configPool.containsKey(key)) {
            requestConfig = configPool.get(key);
        } else {
            requestConfig = org.apache.http.client.config.RequestConfig.custom()
                    .setConnectionRequestTimeout(config.getConnectTime())
                    .setConnectTimeout(config.getRequestTime())
                    .setSocketTimeout(config.getReadTime())
                    .build();
            configPool.put(key, requestConfig);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Create http client for pool manager");
        }
        return HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                // ignore ssl
                .setSSLSocketFactory(getSslSocketFactory())
                // set pool connection
                .setConnectionManager(createPoolingHttpClientConnectionManager())
                // set retry handler
                .setRetryHandler(new CommonHttpRequestRetryHandler())
                // close pool client share
                .setConnectionManagerShared(false)
                // start evict expired connection thread
                .evictExpiredConnections()
                .evictIdleConnections(2, TimeUnit.SECONDS)
                .build();
    }
}
